package com.sinosoft.cloud.access.transformer;

import com.sinosoft.cloud.access.entity.Head;
import com.sinosoft.cloud.access.entity.TranData;
import com.sinosoft.cloud.access.transformer.function.SaxFunction;
import com.sinosoft.cloud.common.SpringContextUtils;
import com.sinosoft.utility.ExceptionUtils;
import net.sf.saxon.Configuration;
import net.sf.saxon.TransformerFactoryImpl;
import net.sf.saxon.s9api.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import static com.sinosoft.cloud.access.AccessConfiguration.BASE_PACKAGE;


/**
 * cloud-access
 *
 * @title: cloud-access
 * @package: com.sinosoft.cloud.access.transformer.XsltTrans
 * @author: yangming
 * @date: 2017-11-26 18:51:52
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class XsltTrans implements Trans {
    private static final Log logger = LogFactory.getLog(XsltTrans.class);
    private static TransformerFactory transformerFactory = null;
    private static Processor proc;

    static {
        Configuration configuration = new Configuration();
        proc = new Processor(false);
        ExtensionFunction[] functions = SaxFunction.getFunctions();
        for (ExtensionFunction function : functions) {
            proc.registerExtensionFunction(function);
        }
        configuration.setProcessor(proc);
        transformerFactory = new TransformerFactoryImpl(configuration);
    }


    private static final String HEAD = "Head.xsl";
    private static final String BODY_IN = "Body_In.xsl";
    private static final String BODY_OUT = "Body_Out.xsl";

    private static HashMap<String, XsltExecutable> xslCache = new HashMap();

    private String accessName;
    private Boolean needRouter = false;

    /**
     * 处理监听
     */
    Map listenerMap = SpringContextUtils.getBeansByClass(TransListener.class);

    /**
     * 报文转化成 TranData
     *
     * @param msg
     * @return
     */
    @Override
    public TranData msgToTranData(String msg) {
        Head head = headerTrans(msg);
        /*根据报文头的交易编码，获取转换模板*/
        Resource xslFile = getBodyInXsl(head.getTransCode());
        /*根据转换模板来 把报文体转换为标准对象*/
        TranData tranData = bodyTrans(msg, xslFile);
        /*把报文头的数据放入TranData*/
        tranData.setHead(head);
        return tranData;
    }

    /**
     * @param tranData
     * @return
     */
    @Override
    public String tranDataToMsg(TranData tranData) {
        /*根据交易编码获取返回报文的转化模板*/
        Resource xslFile = getBodyOutXsl(tranData.getHead().getTransCode());
        /*将标准报文转化为银行所需要的报文，在return的过程中，会将TranData先转化为xml字符串，然后使用xsl进行转化*/
        String returnBodyOut = returnTrans(tranData, xslFile);
        return returnBodyOut;

    }

    /**
     * 前置规则校验
     *
     * @param tranData
     * @return
     */
    private String msgValication(TranData tranData) {
        Resource xslFile;
        /*进行前置规则校验*/
        Validation validation = new Validation(getAccessName());
        if (!validation.check(tranData)) {
            if (null != validation.getErrors()) {
                String error = validation.getErrors().get(0);
                logger.error("接收到的报文数据异常：" + error);

                xslFile = getBodyOutXsl(tranData.getHead().getTransCode());
                TranData returnTranData = new TranData();
                tranData.getHead().setFlag("1");
                tranData.getHead().setDesc(error);
                returnTranData.setHead(tranData.getHead());
                String returnBodyOut = returnTrans(returnTranData, xslFile);
                return returnBodyOut;
            }
        }
        return null;
    }

    /**
     * 接收消息的时间处理机制. 监听可以在具体的业务渠道中
     *
     * @param msg
     */
    private void beforeTransform(String msg) {
        if (listenerMap != null) {
            for (Object key : listenerMap.keySet()) {
                TransListener listener =
                        (TransListener) listenerMap.get(key);
                if (listener != null) {
                    listener.beforeTransEvent(msg);
                }
            }
        }
    }

    /**
     * 接收消息的时间处理机制. 监听可以在具体的业务渠道中
     *
     * @param msg
     * @return
     */
    @Override
    public String afterTransform(String msg) {
        if (listenerMap != null) {
            for (Object key : listenerMap.keySet()) {
                TransListener listener =
                        (TransListener) listenerMap.get(key);
                if (listener != null) {
                    String strXml = listener.afterTransEvent(msg);
                    return strXml;
                }
            }
        }
        return msg;
    }


    /**
     * 新的转化引擎
     *
     * @param strXml
     * @param xslFile
     * @return
     */
    public String xslTransform(String strXml, Resource xslFile) {
        Serializer out = null;
        StringWriter writer = new StringWriter(1024);
        XsltTransformer trans = null;
        StringReader xmlReader = null;
        try {
            XsltExecutable exp = xslCache.get(xslFile.getURL().getPath());
            if (exp == null) {
                StreamSource xslStream = new StreamSource(xslFile.getInputStream());
                XsltCompiler comp = proc.newXsltCompiler();
                exp = comp.compile(xslStream);
                xslCache.put(xslFile.getURL().getPath(), exp);
            }
            xmlReader = new StringReader(strXml);
            StreamSource xmlStream = new StreamSource(xmlReader);
            if (logger.isDebugEnabled()) {
                logger.debug("解析xml:" + strXml);
            }
            out = proc.newSerializer();
            out.setOutputWriter(writer);
            trans = exp.load();
            trans.setSource(xmlStream);
            trans.setDestination(out);
            trans.transform();
            String result = writer.toString();
            return result;
        } catch (FileNotFoundException sae) {
            logger.warn("未找到转换模板，非互联网核心交易！" + sae.getMessage(), sae);
            logger.warn(ExceptionUtils.exceptionToString(sae));
            try {
                xmlReader.close();
                out.close();
                writer.close();
                trans.close();
            } catch (Exception e) {
            }
        } catch (Exception sae) {
            logger.error("XML报文转化失败！" + sae.getMessage(), sae);
            logger.error(ExceptionUtils.exceptionToString(sae));
            try {
                xmlReader.close();
                out.close();
                writer.close();
                trans.close();
            } catch (Exception e) {
            }
        } finally {
            try {
                out.close();
                writer.close();
                trans.close();
            } catch (Exception e) {
            }
        }
        return null;
    }


    /**
     * 获取渠道名称
     *
     * @return
     */
    public String getAccessName() {
        return accessName;
    }

    /**
     * 设置去打名称
     *
     * @param accessName
     */
    @Override
    public void setAccessName(String accessName) {
        this.accessName = accessName;
    }


    /**
     * 报文头处理
     *
     * @param msg
     * @return
     */
    @Override
    public Head headerTrans(String msg) {
        if (logger.isDebugEnabled()) {
            logger.debug("开始进行报文头处理，原始报文为：" + msg);
        }
        Resource xslFile = getHeadXsl();
        String stdHead = xslTransform(msg, xslFile);
        Head head = XmlUtil.getBean(stdHead, Head.class);
        return head;
    }

    private Resource getHeadXsl() {
        ClassPathResource classPathResource = getXslResource(null, HEAD);
        return classPathResource;

    }


    /**
     * 进行报文头转换
     *
     * @param msg
     * @param xslFile
     * @return
     */
    @Override
    public TranData bodyTrans(String msg, Resource xslFile) {

        String stdMsg = xslTransform(msg, xslFile);
        TranData tranData = null;
        try {
            tranData = XmlUtil.getBean(stdMsg, TranData.class);
        } catch (Exception e) {
            tranData = new TranData();
            e.printStackTrace();
            logger.warn("标准报文转换TranData失败：" + ExceptionUtils.exceptionToString(e));
        }
        return tranData;
    }


    /**
     * 获取报文体的转化文件
     *
     * @param tranNo
     * @return
     */
    private Resource getBodyInXsl(String tranNo) {
        ClassPathResource classPathResource = getXslResource(tranNo, BODY_IN);
        return classPathResource;

    }

    /**
     * 报文返回转化xsl文件获取
     *
     * @param tranNo
     * @return
     */
    @Override
    public Resource getBodyOutXsl(String tranNo) {
        ClassPathResource classPathResource = getXslResource(tranNo, BODY_OUT);
        return classPathResource;
    }

    /**
     * 获取xsl文件.
     * xsl存储逻辑:
     * com/sinosoft/cloud/access/   +   xsl/    +    [交易编码]    +    [BODY_IN]或[BODY_OUT]
     * 例如:
     * com/sinosoft/cloud/access/abc/xsl/1002/Body_In.xsl
     *
     * @param tranNo
     * @param xslType
     * @return
     */
    private ClassPathResource getXslResource(String tranNo, String xslType) {

        String xslPath = "";
        if (tranNo == null) {
            xslPath = BASE_PACKAGE + accessName + "/xsl/" + xslType;
        } else {
            xslPath = BASE_PACKAGE + accessName + "/xsl/" + tranNo + "/" + xslType;
        }
        ClassPathResource resource = new ClassPathResource(xslPath);
//        if (logger.isDebugEnabled()) {
//            if (resource != null) {
//                try {
//                    logger.debug("交易编码:" + tranNo + ";xslType:" + xslType + ";文件路径:" + resource.getFile().getAbsolutePath());
//                } catch (IOException e) {
//                    e.printStackTrace();
//                    logger.error("没有获取到对交易编码:" + tranNo + " 对应的 xsl " + xslType);
//                }
//            }
//        }
        return resource;
    }

    @Override
    public String returnTrans(TranData msg, Resource xslFile) {
        String strMsg = XmlUtil.getString(msg);
        String returnBankStr = xslTransform(strMsg, xslFile);
        return returnBankStr;
    }

    public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, TransformerException {


//        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//        DocumentBuilder db = dbf.newDocumentBuilder();
//
//        ClassPathResource xml = new ClassPathResource("data.xml");
//        ClassPathResource xsl = new ClassPathResource("data.xsl");
//
//
//        String strXml = "";
//        try {
//            StringBuffer str = new StringBuffer(1024);
//            FileInputStream fs = new FileInputStream(xml.getFile());
//            InputStreamReader isr;
//            isr = new InputStreamReader(fs);
//            BufferedReader br = new BufferedReader(isr);
//            try {
//                String data = "";
//                while ((data = br.readLine()) != null) {
//                    str.append(data + " ");
//                }
//                strXml = str.toString();
//            } catch (Exception e) {
//                str.append(e.toString());
//            }
//        } catch (IOException es) {
//        }
//
//        while (true) {
//            XsltTrans xsltTrans = new XsltTrans();
//            xsltTrans.setAccessName("abc");
//
//            long a = System.currentTimeMillis();
//            String result = xsltTrans.transform(strXml);
//            System.out.println("" + (System.currentTimeMillis() - a));
//        }
    }

    public Boolean getNeedRouter() {
        return needRouter;
    }

    public void setNeedRouter(Boolean needRouter) {
        this.needRouter = needRouter;
    }
}
